#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include "host_list.h"

#define HOST_ALLOC 10

#ifndef O_LARGEFILE
#define O_LARGEFILE 0
#endif /* O_LARGEFILE */

static int search (host_list_t *host_list, const char *host_name, unsigned int *position);
static host_t *insert_host (host_list_t *host_list, const char *host_name);

void host_list_init (host_list_t *host_list)
{
	host_list->default_host = NULL;
	host_list->hosts = NULL;
	host_list->size = 0;
	host_list->used = 0;
}

void host_list_free (host_list_t *host_list)
{
	size_t i;

	host_list->default_host = NULL;

	if (host_list->hosts) {
		for (i = 0; i < host_list->used; i++) {
			if ((host_list->hosts[i].fd != -1) && (host_list->hosts[i].own_access_log)) {
				close (host_list->hosts[i].fd);
			}

			accesslist_free (&(host_list->hosts[i].accesslist));

			rulelist_free (&(host_list->hosts[i].rulelist));
		}

		free (host_list->hosts);
		host_list->hosts = NULL;
	}

	host_list->size = 0;
	host_list->used = 0;
}

int host_list_load (configuration_t *conf, host_list_t *host_list, int dirlisting, int have_authentication, int log_requests)
{
	host_t *host;
	host_t *alias_host;
	const char *name;
	const char *host_name;
	const char *alias_name;
	const char *string;
	size_t i, j;

	/* Free previous configuration. */
	host_list_free (host_list);

	/* Load virtual hosts. */
	for (i = 0; ((name = configuration_get_child (conf, i, "VirtualHosts", NULL)) != NULL); i++) {
		host_name = configuration_get_value (conf, "VirtualHosts", name, "HostName", NULL);
		if ((!host_name) || (!*host_name)) {
			fprintf (stderr, "Host [%s] doesn't have host name.\n", name);
			return -1;
		}

		host = insert_host (host_list, host_name);
		if (!host) {
			fprintf (stderr, "Couldn't allocate memory for host [%s].\n", name);
			return -1;
		}

		host->host_name = host_name;
		host->host_name_length = strlen (host_name);

		accesslist_init (&host->accesslist);
		rulelist_init (&host->rulelist);

		host->document_root = configuration_get_value (conf, "VirtualHosts", name, "DocumentRoot", NULL);
		if ((!host->document_root) || (!host->document_root[0])) {
			fprintf (stderr, "Host [%s] doesn't have document root.\n", name);
			return -1;
		}

		host->document_root_length = strlen (host->document_root);

		host->access_log = configuration_get_value (conf, "VirtualHosts", name, "AccessLog", NULL);

		host->fd = -1;
		host->own_access_log = 1;

		/* Get whether directory listing will be enabled. */
		string = configuration_get_value (conf, "VirtualHosts", name, "DirectoryListing", NULL);
		if ((!string) || (!*string)) {
			host->dirlisting = dirlisting;
		} else if (strcasecmp (string, "Enabled") == 0) {
			host->dirlisting = 1;
		} else if (strcasecmp (string, "Disabled") == 0) {
			host->dirlisting = 0;
		} else {
			host->dirlisting = dirlisting;
		}

		/* Load access list. */
		string = configuration_get_value (conf, "VirtualHosts", name, "Authentication", NULL);
		if ((!string) || (!*string)) {
			host->have_authentication = have_authentication;
		} else if (strcasecmp (string, "Enabled") == 0) {
			host->have_authentication = 1;
		} else if (strcasecmp (string, "Disabled") == 0) {
			host->have_authentication = 0;
		} else {
			host->have_authentication = have_authentication;
		}

		if (host->have_authentication) {
			if (accesslist_load (&host->accesslist, conf, name) < 0) {
				fprintf (stderr, "Couldn't load access list for host: %s.\n", name);
				return -1;
			}
		}

		/* Get whether we have to log requests. */
		string = configuration_get_value (conf, "VirtualHosts", name, "LogRequests", NULL);
		if ((!string) || (!*string)) {
			host->log_requests = log_requests;
		} else if (strcasecmp (string, "Enabled") == 0) {
			host->log_requests = 1;
		} else if (strcasecmp (string, "Disabled") == 0) {
			host->log_requests = 0;
		} else {
			host->log_requests = log_requests;
		}

		/* Load rule list. */
		if (rulelist_load (&host->rulelist, name, conf) < 0) {
			fprintf (stderr, "Couldn't load rule list for host: %s.\n", name);
			return -1;
		}

		/* Default host? */
		if (strcasecmp (name, "default") == 0) {
			host_list->default_host = host;
		}

		/* Load aliases. */
		for (j = 0; ((alias_name = configuration_get_child (conf, j, "VirtualHosts", name, "Aliases", NULL)) != NULL); j++) {
			alias_host = insert_host (host_list, alias_name);
			if (!alias_host) {
				fprintf (stderr, "Couldn't allocate memory for host [%s].\n", alias_name);
				return -1;
			}

			alias_host->host_name = alias_name;
			alias_host->host_name_length = strlen (alias_name);

			accesslist_init (&alias_host->accesslist);
			rulelist_init (&alias_host->rulelist);

			alias_host->document_root = host->document_root;
			alias_host->document_root_length = host->document_root_length;

			alias_host->access_log = host->access_log;

			alias_host->fd = -1;
			alias_host->own_access_log = 1;

			alias_host->dirlisting = host->dirlisting;

			alias_host->have_authentication = host->have_authentication;
			if (alias_host->have_authentication) {
				if (accesslist_load (&alias_host->accesslist, conf, name) < 0) {
					fprintf (stderr, "Couldn't load access list for host: %s.\n", name);
					return -1;
				}
			}

			alias_host->log_requests = host->log_requests;

			if (rulelist_load (&alias_host->rulelist, name, conf) < 0) {
				fprintf (stderr, "Couldn't load rule list for host: %s.\n", name);
				return -1;
			}
		}
	}

	/* If there is not default host... */
	if (!host_list->default_host) {
		fprintf (stderr, "Default host not present.\n");
		return -1;
	}

	for (i = 0; i < host_list->used; i++) {
		if (host_list->hosts[i].log_requests) {
			if ((host_list->hosts[i].access_log) && (host_list->hosts[i].access_log[0])) {
				for (j = 0; j < i; j++) {
					if (strcmp (host_list->hosts[i].access_log, host_list->hosts[j].access_log) == 0) {
						host_list->hosts[i].fd = host_list->hosts[j].fd;
						host_list->hosts[i].own_access_log = 0;
						break;
					}
				}

				if (i == j) {
					host_list->hosts[i].fd = open (host_list->hosts[i].access_log, O_CREAT | O_WRONLY | O_APPEND | O_LARGEFILE, 0644);
					if (host_list->hosts[i].fd < 0) {
						fprintf (stderr, "Couldn't open log file: [%s].\n", host_list->hosts[i].access_log);
						return -1;
					}

					host_list->hosts[i].own_access_log = 1;
				}
			}
		}
	}

	return 0;
}

int search (host_list_t *host_list, const char *host_name, unsigned int *position)
{
	int i, j, pivot;
	int ret;

	i = 0;
	j = host_list->used - 1;

	while (i <= j) {
		pivot = (i + j) / 2;

		ret = strcmp (host_name, host_list->hosts[pivot].host_name);
		if (ret < 0) {
			j = pivot - 1;
		} else if (ret == 0) {
			*position = (unsigned int) pivot;
			return 0;
		} else {
			i = pivot + 1;
		}
	}

	*position = (unsigned int) i;

	return -1;
}

host_t *insert_host (host_list_t *host_list, const char *host_name)
{
	host_t *hosts;
	unsigned int position;
	size_t size;

	if (search (host_list, host_name, &position) < 0) {
		if (host_list->used == host_list->size) {
			size = host_list->size + HOST_ALLOC;
			hosts = (host_t *) realloc (host_list->hosts, size * sizeof (host_t));
			if (!hosts) {
				return NULL;
			}

			host_list->hosts = hosts;
			host_list->size = size;
		}

		if (position < host_list->used) {
			if ((host_list->default_host) && (&(host_list->hosts[position]) <= host_list->default_host)) {
				host_list->default_host++;
			}

			memmove (&(host_list->hosts[position + 1]), &(host_list->hosts[position]), (host_list->used - position) * sizeof (host_t));
		}

		host_list->used++;
	}

	return &(host_list->hosts[position]);
}

host_t *host_list_search_host (host_list_t *host_list, const char *host_name)
{
	unsigned int position;

	if ((!host_name) || (!*host_name)) {
		return NULL;
	}

	if (search (host_list, host_name, &position) < 0) {
		return NULL;
	}

	return &(host_list->hosts[position]);
}
